package cn.jh.microservises.support.gateway.filters.pre;


import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import static org.springframework.util.StringUtils.hasText;


public class AuthenticationHeaderFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(AuthenticationHeaderFilter.class);

    private static final String secretKey = "Yqilai-20160523";

    private static final String[][] preAuthenticationIgnoreUris = {
            {"/v1.0/authentication/sendSms", "*"},
            {"/v1.0/authentication/register", "*"},
            {"/v1.0/authentication/login", "*"},
            {"/v1.0/authentication/restster/wx", "*"},
            {"/v1.0/authentication/wx/user", "*"},
            {"/v1.0/facade/openid", "*"},
            {"/v1.0/facade/gatherdetail", "*"},
            {"/v1.0/facade/detailinfo", "*"},
            {"/v1.0/facade/detailinfohistory", "*"},
            {"/v1.0/facade/edit/gathering", "*"},
            {"/v1.0/facade/quit/success", "*"},
            {"/v1.0/facade/music/productype", "*"},
            {"/v1.0/facade/code", "*"},
            {"/v1.0/facade/token", "*"},
            {"/v1.0/facade/index", "*"},
            {"/v1.0/facade/share/friend", "*"},
            {"/v1.0/relationship/friend", "*"},
            {"/v1.0/relationship/judge", "*"},
            {"/v1.0/facade/test/test1", "*"},
            {"/v1.0/restaurant/baidu/view", "*"},
            {"/v1.0/facade/appshare", "*"},
            {"/v1.0/facade/historydetailinfo", "*"},
            {"/v1.0/facade/schedulephotoinfo", "*"},
            {"/v1.0/facade/join/success", "*"},
            {"/v1.0/facade/join/createsuccess", "*"},
            {"/v1.0/facade/intro", "*"},
            {"/v1.0/facade/gathering", "*"},
            {"/v1.0/facade/invitegathering", "*"},
            {"/v1.0/facade/first", "*"},
            {"/v1.0/facade/session", "*"},
            {"/v1.0/facade/version", "*"},
            {"/v1.0/facade/user/id", "*"},
            {"/v1.0/facade/user/friends", "*"},
            {"/v1.0/facade/user/update", "*"},
            {"/v1.0/facade/user/view", "*"},
            {"/v1.0/facade/hx/detail", "*"},
            {"/v1.0/facade/signature/token", "*"},
            {"/v1.0/facade/recommend", "*"},
            {"/v1.0/restaurant/first", "*"},
            {"/v1.0/restaurant/session", "*"},
            {"/v1.0/restaurant/api/ip2place", "*"},
            {"/v1.0/restaurant/api/maphtml5", "*"},
            {"/v1.0/restaurant/api/baidumap", "*"},
            {"/v1.0/restaurant/api/latlng2address", "*"},
            {"/v1.0/restaurant/api/address2latlng", "*"},
            {"/v1.0/restaurant/api/searchKeyword", "*"},
            {"/v1.0/restaurant/api/searchCater", "*"},
            {"/v1.0/restaurant/api/webim", "*"},
            {"/v1.0/facade/Restaurant/webIM", "*"},
            {"/v1.0/restaurant/api/searchLife", "*"},
            {"/v1.0/restaurant/api/maphtml1", "*"},
            {"/v1.0/restaurant/api/maphtml2", "*"},
            {"/v1.0/restaurant/api/choujiang", "*"},
            {"/v1.0/restaurant/api/maphtml3", "*"},
            {"/v1.0/restaurant/api/maphtml4", "*"},
            {"/v1.0/restaurant/api/maphtml6", "*"},
            {"/v1.0/restaurant/api/maphtml7", "*"},
            {"/v1.0/restaurant/api/maphtml8", "*"},
            {"/v1.0/restaurant/api/maphtml9", "*"},
            {"/v1.0/restaurant/index", "*"},
            {"/v1.0/restaurant/login", "*"},
            {"/v1.0/user/users", "*"},
            {"/v1.0/user/update", "*"},
            {"/v1.0/restaurant/api/placeSuggestion", "*"},
            {"/v1.0/restaurant/api/excel", "*"},
            {"/v1.0/relationship/group/accept/excel", "*"},
            {"/v1.0/facade/makeExcel", "*"},
            {"/v1.0/facade/userList", "*"},
            {"/v1.0/facade/invite/wx", "*"},
            {"/v1.0/restaurant/api/easyEditor", "*"},
            {"/v1.0/restaurant/api/placedetail", "*"},
            {"/v1.0/restaurant/api/startPay", "*"},
            {"/v1.0/restaurant/api/paySuccess", "*"},
            {"/v1.0/restaurant/api/paySuccessPage", "*"},
            {"/v1.0/restaurant/api/shoppingcart", "*"},
            {"/v1.0/restaurant/api/moreshoppingcart", "*"},
            {"/v1.0/restaurant/api/useWeixinPay", "*"},
            {"/v1.0/restaurant/api/useAlipay", "*"},
            {"/v1.0/restaurant/api/consumeProduct", "*"},
            {"/v1.0/restaurant/api/showProductDetail", "*"},
            {"/v1.0/restaurant/api/createOrder", "*"},
            {"/v1.0/restaurant/api/createOrders", "*"},
            {"/v1.0/restaurant/api/orderbytime", "*"},
            {"/v1.0/user/addLuckyCount", "*"},
            {"/v1.0/restaurant/card", "*"},
            {"/android_apk", "*"},
            {"/v1.0/restaurant/product/id", "*"},
            {"/v1.0/user/dailySign", "*"},
            {"/v1.0/user/usePatiCoin", "*"},
            {"/v1.0/restaurant/api/PatiCoinPay", "*"},
            {"/v1.0/restaurant/api/shoppingcart2", "*"},
            {"/v1.0/gathering/picture", "*"},
            {"/v1.0/facade/invite/example", "*"},
            {"/v1.0/paymentchannel/order", "*"},
            {"/v1.0/user/app/Platform/query", "*"},
            {"/v1.0/user/shop/platform", "*"}
    };

    //	所有request都需要验证Token，除了上面ignore的
    private static final String[][] preAuthenticationMustUris = {
            {"/", "*"}
    };

    //If request method is POST
//	private static final String[][] preAuthenticationMustUris = {
//		{"/user", "POST"}
//	};

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        try {
            request.setCharacterEncoding("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            throw new InternalError(ex);
        }

        String requestURI = request.getRequestURI();
        String method = request.getMethod();

        log.trace("====AuthenticationHeaderFilter.shouldFilter: {} {}", method, requestURI);
        for (String[] preAuthenticationIgnoreUri : preAuthenticationIgnoreUris) {
            if (requestURI.startsWith(preAuthenticationIgnoreUri[0].toLowerCase()) &&
                    (preAuthenticationIgnoreUri[1].equals("*") || method.equalsIgnoreCase(preAuthenticationIgnoreUri[1]))) {
                log.trace("this will be not use filter");
                return false;
            }
        }

        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String requestURI = request.getRequestURI();
        String method = request.getMethod();

        log.trace("====AuthenticationHeaderFilter.run: {} {}", method, requestURI);

        // 1. clear userInfo from HTTP header to avoid fraud attack
        ctx.addZuulRequestHeader("user-info", "");

        // 2. verify the passed user token
        String userToken = StringUtils.trimToNull(request.getHeader("UserToken"));
        if (userToken == null) {
            userToken = fromHeader(request);
            if (userToken == null) { // Last: parameter
                userToken = request.getParameter("token");
            }
        }

        log.trace(String.format("====AuthenticationHeaderFilter.run - UserToken: %s", userToken));
        Claims claims;
        String userInfo = null;
        if (userToken != null && !"null".equals(userToken)) {
            try {
                claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(userToken).getBody();

                Object uid = claims.get("userId");
                String userId;
                if (uid instanceof String) {
                    userId = (String) uid;
                } else {
                    userId = String.valueOf(uid);
                }

                String userName = StringUtils.trimToEmpty((String) claims.get("userName"));
                userInfo = String.format("{\"id\":\"%s\",\"name\":\"%s\"}", userId, userName);
            } catch (SignatureException e) {
                this.stopZuulRoutingWithError(ctx, HttpStatus.UNAUTHORIZED, "Invalid User Token for the API (" + requestURI + ")");
                return null;
            } catch (ExpiredJwtException e) {
                this.stopZuulRoutingWithError(ctx, HttpStatus.UNAUTHORIZED, "Expired User Token for the API (" + requestURI + ")");
                return null;
            }
        }

        log.trace("=====AuthenticationHeaderFilter.run - userInfo: {}", userInfo);
        // 3. set userInfo to HTTP header
        if (userInfo != null) {

            String encodeUserInfo;

            try {
                encodeUserInfo = URLEncoder.encode(userInfo, "UTF-8");
            } catch (UnsupportedEncodingException ex) {
                throw new InternalError(ex);
            }

            ctx.addZuulRequestHeader("user-info", encodeUserInfo);
        }

        return null;
    }

    private String fromHeader(HttpServletRequest request) {
        String authzHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (!hasText(authzHeader) || !authzHeader.contains(" ")) {
            return null;
        }
        int wIdx = authzHeader.indexOf(' ');
        if (wIdx > 0 && wIdx < authzHeader.length() - 1) {
            return authzHeader.substring(wIdx + 1);
        }
        return null;
    }

    private void stopZuulRoutingWithError(RequestContext ctx, HttpStatus status, String responseText) {

        ctx.removeRouteHost();
        ctx.setResponseStatusCode(status.value());
        ctx.setResponseBody(responseText);
        ctx.setSendZuulResponse(false);
    }
}
